﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using Metaspec;

// Project and solution namespace example.

namespace NamespacesExample
{
	class Program
	{
		static void Main(string[] args)
		{
			project_variant();
			shared_assemblies_variant();
			solution_variant();
		}

		// Variant A
		// Assembly and project entities stored in project namespaces.
		// Solution is not required for working with projects, but can be used.

		static void project_variant()
		{
			//creating project1
			ICsProject project1 = ICsProjectFactory.create(project_namespace.pn_project_namespace);
			project1.setBuildEntityModel(true);

			{
				//adding assemblies
				string assembly_path = Assembly.GetAssembly(typeof(object)).Location;
				byte[] assembly_buffer = File.ReadAllBytes(assembly_path);
				IExternalAssemblyModule module = IExternalAssemblyModuleFactory.create(assembly_buffer, assembly_path);
				project1.addExternalAssemblyModules(new IExternalAssemblyModule[] { module }, false, null);

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project11.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project1.addFiles(file);
			}

			//creating project2
			ICsProject project2 = ICsProjectFactory.create(project_namespace.pn_project_namespace);
			project2.setBuildEntityModel(true);

			{
				//adding assemblies
				string assembly_path = Assembly.GetAssembly(typeof(object)).Location;
				byte[] assembly_buffer = File.ReadAllBytes(assembly_path);
				IExternalAssemblyModule module = IExternalAssemblyModuleFactory.create(assembly_buffer, assembly_path);
				project2.addExternalAssemblyModules(new IExternalAssemblyModule[] { module }, false, null);

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project12.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project2.addFiles(file);

				//adding reference to project1
				project2.addProjectReferences(new ProjectReference(project1));
			}

			project1.parse(true, false);
			project2.parse(true, false);
		}

		// Variant B
		// Assemblies can be stored in solution (shared assemblies) or in project assemblies, project entities stored in project namespaces.
		// Solution used only for store shared assemblies.

		static void shared_assemblies_variant()
		{
			ISolution solution = ISolutionFactory.create();

			//adding assemblies
			string assembly_path = Assembly.GetAssembly(typeof(object)).Location;
			byte[] assembly_buffer = File.ReadAllBytes(assembly_path);
			IExternalAssemblyModule module = IExternalAssemblyModuleFactory.create(assembly_buffer, assembly_path);
			solution.addExternalAssemblyModules(new IExternalAssemblyModule[] { module }, true, null);
			IExternalAssembly assembly = module.getExternalAssembly();

			//creating project1
			ICsProject project1 = ICsProjectFactory.create(project_namespace.pn_project_namespace);
			project1.setBuildEntityModel(true);

			{
				//adding assemblies
				project1.addAssemblyReferences(new ExternalAssemblyReference(assembly));

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project21.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project1.addFiles(file);
			}

			//creating project2
			ICsProject project2 = ICsProjectFactory.create(project_namespace.pn_project_namespace);
			project2.setBuildEntityModel(true);

			{
				//adding assemblies
				project2.addAssemblyReferences(new ExternalAssemblyReference(assembly));

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project22.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project2.addFiles(file);

				//adding reference to project1
				project2.addProjectReferences(new ProjectReference(project1));
			}

			//adding projects and parsing
			solution.addProjects(project1);
			solution.addProjects(project2);
			solution.parse();

			//checking
			CsEntityNamespace solutionNamespace = solution.getNamespace();
			CsEntityNamespace projectNamespace = project1.getNamespace();
			Debug.Assert(projectNamespace != solutionNamespace);

			//entity parent defined in project refers to project namespace
			CsEntity p11s = getEntity(solutionNamespace, "Project21");
			CsEntity p11p = getEntity(projectNamespace, "Project21");
			CsEntityType c = (CsEntityType)getEntity(projectNamespace, "Project21", "C");
			Debug.Assert(c.parent != p11s);
			Debug.Assert(c.parent == p11p);

			//solution and project namespaces are different
			CsEntity syss = getEntity(solutionNamespace, "System");
			CsEntity sysp = getEntity(projectNamespace, "System");
			Debug.Assert(syss != sysp);
			//entity imported from assembly not changed
			CsEntity objs = getEntity(solutionNamespace, "System", "Object");
			CsEntity objp = getEntity(projectNamespace, "System", "Object");
			Debug.Assert(objs == objp);
			//entity parent defined in imported assembly refers to solution namespace
			Debug.Assert(objp.parent == syss);
		}

		// Variant C
		// Assemblies and project entities stored in solution. Project namespaces not used.

		static void solution_variant()
		{
			ISolution solution = ISolutionFactory.create();

			//adding assemblies
			string assembly_path = Assembly.GetAssembly(typeof(object)).Location;
			byte[] assembly_buffer = File.ReadAllBytes(assembly_path);
			IExternalAssemblyModule module = IExternalAssemblyModuleFactory.create(assembly_buffer, assembly_path);
			solution.addExternalAssemblyModules(new IExternalAssemblyModule[] { module }, true, null);
			IExternalAssembly assembly = module.getExternalAssembly();

			//creating project1
			ICsProject project1 = ICsProjectFactory.create(project_namespace.pn_solution_namespace);
			project1.setBuildEntityModel(true);

			{
				//adding assemblies
				project1.addAssemblyReferences(new ExternalAssemblyReference(assembly));

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project31.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project1.addFiles(file);
			}

			//creating project2
			ICsProject project2 = ICsProjectFactory.create(project_namespace.pn_solution_namespace);
			project2.setBuildEntityModel(true);

			{
				//adding assemblies
				project2.addAssemblyReferences(new ExternalAssemblyReference(assembly));

				//adding C# file
				string directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), @"..\..");
				string path = Path.Combine(directory, "Project32.cs");
				char[] buffer = File.ReadAllText(path).ToCharArray();
				ICsFile file = ICsFileFactory.create(buffer, path);
				project2.addFiles(file);

				//adding reference to project1
				project2.addProjectReferences(new ProjectReference(project1));
			}

			//adding projects and parsing
			solution.addProjects(project1);
			solution.addProjects(project2);
			solution.parse();

			//project namespace empty, because they not used.
			Debug.Assert(project1.getNamespace() == null);
			Debug.Assert(project2.getNamespace() == null);

			//namespace can contain types with the same name and defined in different projects
			Debug.Assert(getEntityCount(solution.getNamespace(), "Common", "C") == 2);
			LinkedList<CsEntity> entities = getEntityList(solution.getNamespace(), "Common", "C");
			Debug.Assert((entities.First.Value.assembly == project1.getAssemblyEntity()
					&& entities.First.Next.Value.assembly == project2.getAssemblyEntity())
				|| (entities.First.Value.assembly == project2.getAssemblyEntity()
					&& entities.First.Next.Value.assembly == project1.getAssemblyEntity()));
		}

		static CsEntity getEntity(CsScopeEntity scope, params string[] names)
		{
			CsEntity entity = null;
			for (int i = 0; i != names.Length; ++i)
			{
				entity = scope.scope_table.get_single_id(names[i]);
				if (i != names.Length - 1)
				{
					scope = (CsScopeEntity)entity;
				}
			}
			return entity;
		}

		static LinkedList<CsEntity> getEntityList(CsScopeEntity scope, params string[] names)
		{
			for (int i = 0; i != names.Length; ++i)
			{
				if (i == names.Length - 1)
				{
					return scope.scope_table.get_id_list(names[i]);
				}
				else
				{
					scope = (CsScopeEntity)scope.scope_table.get_single_id(names[i]);
				}
			}
			return null;
		}

		static int getEntityCount(CsScopeEntity scope, params string[] names)
		{
			LinkedList<CsEntity> entity_list = getEntityList(scope, names);
			if (entity_list != null)
			{
				return entity_list.Count;
			}
			return 0;
		}
	}
}
